在很前面的文章中,咱們有簡單的介紹如何使用 WebRTC 來採集聲音與影像,但那時只是很簡單的介紹一下而以,所以接下來的幾篇文章,咱們將要來深入的了解 WebRTC。
這篇文章將要介紹幾個 WebRTC 的基概念,大約分成以下幾個章節:
首先在 Web 通信的世界中,基本上都是所謂的 C/S 架構,也就是所謂的 client 與 server 架構,通常 client 要取資料時就是發送一個請求給 server 然後它會回傳資料回去,其中 ajax 的出現讓我們更能以少量的資源來取得資料,在這階段時都部份都是單向溝通,也就是 client 請求 server。
而在二階段能,人們開始有種需求,例如股票報價網站,人們希望可以看到當有股價變動時,網頁可以也同時更動,這時如果用上面那種模式,那就只能 client 定時的去 server 拿資料,也就是咱們所謂的輪詢
,但這種方法很明顯的非常的浪費資源,你可以呼叫 server 十次,但只有一次才真的有新的資料。而這時webSocket
就用來解決這向事情,它提供了雙向溝通功能,server 就可以透過它,來將資料推給 client。
基本上以上已經解決了 client 與 server 的雙向互動,但這時人們又在想,假設我是做個一對一的聊天工具,那為什麼還需要 server 呢? 不能直接 client 與 client 進行溝通就好呢 ? WebRTC 就是可以幫助我們完成的工具,它就是用來專門處理瀏覽器與瀏覽器之間的即時溝通。
備註:雖然說是 cleint 與 client 直接進行溝通,但不是說不需要 server,後面會說明。
WebRTC 的存在目的,就是為了讓瀏覽器不需要認何 plugin 就可以快速的開發出 P2P 語音或視頻對話 。
首先在咱們來看看 WebRTC 的基本架構,如下圖。
圖片來源:官網
最外層紫色
的地方就是我們所使用的 API 部份,基本上可以分三種:
然後藍色實線
那層的 WebRTC C++ API 是專門給瀏覽器開發商更容易的實作 WebRTC 標準的 WebAPI。
最後藍色虛線
那層由各瀏覽器開發商自行定義實作。
關於 getUserMedia 的相關使用,可以參考筆者的以下兩篇文章。
30-07之Web 如何進行語音與影像採集 ?
30-08之 WebRTC 採集的詳細說明與聲音的加工
在開始傳輸聲音之前,我們需要先進行編碼,接下來就是進行封裝,而封裝時也需要據傳輸協議才能決定要封裝成什麼型式來送貨。接下來這章節就簡單的說明一下 WebRTC 所支援的編碼與傳輸協議。
比較詳細的說明可至筆者的這篇文章『30-03之聲音的編碼與壓縮』
這個編碼只要記得他是瑞士刀就好。
它是一個提供寬帶 (wideband) 與超寬帶 (wideband) 的語音編碼器,大部份使用在 VoIP 與串流應用中,為 WebRTC 預設的語音編碼器。(目前是屬於 google 的)
備註:
上面說的寬帶與超寬帶的意思是指他的採樣率的意思,寬帶是指 16KHz 與超寬帶指的是 32KHz。
它是一個提供窄帶的語音編碼器,也是大部都使用在 VoIP 與串流應用中,它的採樣率為 8HZ。
比較詳細的說明可至筆者的這篇文章『30-05之影像的編碼與壓縮』
它是 Google 收購的 On2 所開發的視頻編碼,它基本上會封裝在 .webm 格式中
它就是 VP8 的進化版。
它使用 RTP/RTCP 協議
此協議詳細內容請看筆者的此篇文章『30-12之 RTP/RTCP 傳輸協議』。
備註: 別忘了它是傳輸層協議
備註: 它可以選擇用 UDP(預設) 或 TCP
這裡問一個問題。
不過我覺得最大的理由在於,某此方面來說 WebRTC 已經可以算是應用層的協議,它讓雙方的瀏覽器都定義好了一定的流程來完成 P2P 溝通這件事情,因此它接下來應該只要在選擇使用什麼傳輸層協議來進行溝通就行了。
要建立一個 WebRTC 的 P2P 基本應用如下圖,你會發現事實上還有一個 Server,但它不是說是 P2P 連線嗎 ?
嗯沒錯,但它是指聲音或影像直接的進行 P2P 傳送,而不是需要經過 Server 來將聲音傳出去。
那這個 Server 是要做什麼用的 ? 在 WebRTC 雙方要建立連線前,誰都不認識對方,這也代表雙方都不知對方在那,而這個 Server 就是讓他們互相認識的交誼廳 ,這在 WebRTC 中又被稱為 Signaling Server 。
建立連線的過程如下圖。
這裡基本上分為兩部份程式碼,一段是 client 端的,另一端為 server 端的。
這一段是建立 Signaling Server,我們使用 nodejs 的 express 來建立,然後我們還有使用到peer
這個套件來幫助我們可以更輕鬆的使用 WebRTC 的 P2P 操作。
下面這段程式碼中,事實上只做了兩件事。
/a
與/b
不同的頁面來模擬不同的用戶)const express = require('express');
const app = express();
const expressPeerServer = require('peer').ExpressPeerServer;
const server = app.listen(9000);
const peerserver = expressPeerServer(server);
app.use('/api', peerserver);
app.get('/a', function(req, res){
res.sendfile(__dirname + "/index-clientA.html");
});
app.get('/b', function(req, res){
res.sendfile(__dirname + "/index-clientB.html");
});
peerserver.on('connection', (id) => {
console.log(`A client connected : ${id}`);
})
peerserver.on('disconnect', (id) => {
console.log(`A client say ~ bye bye : ${id}`);
});
其中比較需要注意的是在使用new Peer(‘A’)
時,這裡面所代的 A 就代表你這用戶的編碼,別人如果要和你進行連線時,就需要使用這個編碼,但是這不是說任何人都可以和你連線,必須是要在此會話中,才能進行連線。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.9/peer.min.js"></script>
<script>
const peer = new Peer('A', { host: 'localhost', port: 9000, path: '/api' });
peer.on('open', function (id) {
console.log('My peer ID is: ' + id);
});
peer.on('connection', (conn) => {
conn.on('open', () => {
// 有任何人加入這個會話時,就會觸發
console.log(`${conn.peer} is connected with me`);
});
conn.on('data', function (data) {
// 當收到訊息時會執行
console.log(`${conn.peer} : ` + data);
conn.send('HI I am A');
});
});
</script>
</body>
</html>
這個是 B 用戶,一但它建立連線後,就會發送與 A 會話的請求peer.connect(‘A’)
,然後連線成功後會在發送一段訊息給 A。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.9/peer.min.js"></script>
<script>
var peer = new Peer('B', { host: 'localhost', port: 9000, path: '/api' });
var conn = peer.connect('A');
peer.on('open', function (id) {
console.log('My peer ID is: ' + id);
});
conn.on('open', () => {
// 與 A 連線後,會發送以下訊息給 A。
conn.send('Hi I am B');
});
conn.on('data', (data) => {
// 送到某人發送的訊息。
console.log(`${conn.peer} : ` + data);
});
</script>
</body>
</html>
全部程式碼中此『傳送門』
npm install
node broker-server.js
127.0.0.1:9000/a
127.0.0.1:9000/b
本篇文章中咱們學習了 WebRTC 的基本概念與架構,並且在簡單的實作一個 P2P 通信範例,而接下來的文章中,咱們要來討探一下上面有說到的 Signaling Server 的一些事情。